package cn.net.sinodata.system.msghandler.parse.entitymapping;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.net.sinodata.common.json.utils.JsonUtils;
import cn.net.sinodata.system.msghandler.exception.DynamicParseException;
import cn.net.sinodata.system.msghandler.exception.MsgParseException;
import cn.net.sinodata.system.msghandler.exception.NoRangeContainsException;
import cn.net.sinodata.system.msghandler.exception.RequiredParamIsNullException;
import cn.net.sinodata.system.msghandler.parse.constants.Constant;
import cn.net.sinodata.system.msghandler.parse.scripthandler.ScriptHandler;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class BaseEntityMapping implements IEntityMapping {

    private static final List<String> NOT_ALLOW_TYPE = Arrays.asList("byte", "short", "int", "long", "float", "double", "char", "boolean");

    /**
     * 生成对应的实体 TODO 如何生成对应的实体信息
     * @param dbRuleTpl 报文解析模板
     * @param result 报文解析 TODO 确认下是否使用Object类型
     * @param clazz
     * @return
     * @param <T>
     * @throws Exception
     */
    @Override
    public <T> T entityMapping(ArrayNode dbRuleTpl, Object result, Class<T> clazz) throws Exception {
        ScriptHandler scriptHandler = new ScriptHandler();
        // 创建实体类对象
        T entity = clazz.newInstance();
        // 实体映射模版解析及实体映射
        for (int i = 0; i < dbRuleTpl.size(); i++) {
            // 获取标签的值
            JsonNode dbRuleJson = dbRuleTpl.get(i);
            String source = dbRuleJson.path(Constant.ENTITY_MAP_TPL_TAG_SOURCE).asText();
            String handler = dbRuleJson.path(Constant.ENTITY_MAP_TPL_TAG_HANDLER).asText();
            Boolean required = dbRuleJson.path(Constant.ENTITY_MAP_TPL_TAG_NOT_NULL).asBoolean(false);
            String format = dbRuleJson.path(Constant.ENTITY_MAP_TPL_TAG_FORMAT).asText();
            JsonNode tmpRangArrTmp = dbRuleJson.path(Constant.ENTITY_MAP_TPL_TAG_RANGE);
            ArrayNode rangArr = tmpRangArrTmp.isArray() ? (ArrayNode)tmpRangArrTmp : null;
            String target = dbRuleJson.path(Constant.ENTITY_MAP_TPL_TAG_TARGET).asText();
            String defaultValue = dbRuleJson.path(Constant.ENTITY_MAP_TPL_TAG_DEFAULT_VALUE).asText();
            JsonNode tmpMappingJson = dbRuleJson.path(Constant.ENTITY_MAP_TPL_TAG_MAPPING);
            ObjectNode mappingJson = tmpMappingJson.isObject() ? (ObjectNode)tmpMappingJson : null;
            //String condition = dbRuleJson.getString(Constant.ENTITY_MAP_TPL_TAG_CONDITION);

            // 获取set方法
            String targetLowerCamelCase = target.substring(0, 1).toLowerCase() + target.substring(1); // 小驼峰
            String targetUpperCamelCase = target.substring(0, 1).toUpperCase() + target.substring(1); // 大驼峰
            Field field;
            try {
                field = clazz.getDeclaredField(targetLowerCamelCase);
            } catch (NoSuchFieldException e) {
                throw new MsgParseException("target=[" +target+ "]未匹配到对应实体类中的方法:"+targetLowerCamelCase, e);
            }
            Class<?> fieldType = field.getType();
            Method method = clazz.getMethod("set" + targetUpperCamelCase, fieldType);

            // 获取数据值信息
            String msgValue = null;
            if (StrUtil.isNotEmpty(source)) {
                msgValue = getValueByPath(result, source);
            } else if (StrUtil.isNotEmpty(handler)) {
                msgValue = scriptHandler.caller(result, handler);
            } else if (StrUtil.isEmpty(defaultValue)) {
                throw new MsgParseException("模版配置错误,缺少source或handler标签:" + dbRuleJson);
            } else {
                // nothing to do
            }

            // 校验必输性
            if (required && StrUtil.isEmpty(msgValue)) {
                throw new RequiredParamIsNullException("必输参数为空：" + dbRuleJson);
            }

            // 设置默认值
            if (StrUtil.isEmpty(msgValue) && StrUtil.isNotEmpty(defaultValue)) {
                msgValue = defaultValue;
            }

            // TODO 表示式校验

            // 区间匹配
            if (rangArr!= null) {
                if (!rangArr.has(msgValue)) {
                    throw new NoRangeContainsException(rangArr + "不包含" + msgValue);
                }
            }

            // 映射
            if (mappingJson != null && mappingJson.hasNonNull(msgValue)) {
                String mappingValue = mappingJson.get(msgValue).asText();
                if (StrUtil.isNotEmpty(mappingValue)) {
                    msgValue = mappingValue;
                }
            }

            // 类型转换
            Object entityValue = typeConverter(msgValue, fieldType, format);
            // 获取结果为空，无需存储在实体中
            if (entityValue == null) {
                //method.invoke(entity, (Object) null);
                continue;
            }
            method.invoke(entity, entityValue);
        }
        return entity;
    }

    /**
     * 生成对应的实体
     * @param dbRuleTpl 报文解析模板
     * @param result 报文解析 TODO 确认下是否使用Object类型
     * @return
     * @throws Exception
     */
    @Override
    public Map<Class, Object> multiEntityMapper(ArrayNode dbRuleTpl, Object result) throws Exception {
        // 记录解析的实体
        HashMap<Class, Object> entityMap = new HashMap<>();

        // 记录分割的模板
        HashMap<String, ArrayNode> tplMap = new HashMap<>();
        // 分割模板
        for (int i = 0; i < dbRuleTpl.size(); i++) {
            ObjectNode dbRuleJson = (ObjectNode)dbRuleTpl.get(i);
            String entityName = dbRuleJson.get(Constant.ENTITY_MAP_TPL_TAG_ENTITY).asText();
            if (tplMap.get(entityName) == null) {
                tplMap.put(entityName, JsonUtils.getObjectMapper().createArrayNode());
            }
            ArrayNode tpl = tplMap.get(entityName);
            tpl.add(dbRuleJson);
        }

        // 循环解析
        for (String entityName : tplMap.keySet()) {
            Class<?> clazz = Class.forName("cn.net.sinodata.system.domain." + entityName);
            Object entity = entityMapping(tplMap.get(entityName), result, clazz);
            entityMap.put(clazz, entity);
        }
        return entityMap;
    }

    /**
     * 类型转换
     * @param msgValue 待转换值
     * @param fieldType 需转换为的类型
     * @param otherParams 转换需要的其他参数（如时间类型需要转换格式）
     * @return
     * @throws Exception
     */
    public <T> T  typeConverter(String msgValue, Class<T> fieldType, String... otherParams) throws Exception {
        if (StrUtil.isEmpty(msgValue)) {
            return null;
        }

        // 先进行自定义类型转换
        T entityValue = customTypeConverter(msgValue, fieldType, otherParams);
        if (entityValue != null) {
            return entityValue;
        }

        // 日期类型处理
        if (fieldType == java.util.Date.class) {
            if (otherParams.length == 0 || StrUtil.isEmpty(otherParams[0])) {
                throw new DynamicParseException(msgValue + " 日期格式化时，未找到格式化标签");
            }
            String format = otherParams[0];
            DateTime dateParse = DateUtil.parse(msgValue, format);
            return (T) dateParse;
        }

        // 进行统一的类型转换
        entityValue = Convert.convert(fieldType, msgValue);
        return entityValue;
    }

    /**
     * 自定义类型处理，如果需要进行自定义的类型，请在子类中**重写该方法**
     * @param msgValue
     * @param fieldType
     * @param otherParams
     * @param <T>
     * @return
     */
    <T> T customTypeConverter(String msgValue, Class<T> fieldType, String... otherParams) {
        return null;
    }

    /**
     * 各实体映射类，需要实现的方法
     * @param result msgParse解析之后的结果（可以在方法内部转换为对应的类型）
     * @param source 获取result中数据的path
     * @return
     */
    abstract String getValueByPath(Object result, String source);

    /**
     * 实体类检查方法，不允许存在基础类型的属性
     * 包含：byte, short, int, long, float, double,char,boolean
     * @param clazz 需检查类的clazz
     * @throws Exception
     */
    public static void entityCheck(Class<?> clazz) throws Exception {
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            String methodName = method.getName();
            if (methodName.startsWith("is")) {
                throw new DynamicParseException("禁止使用is开头");
            }
            if (!methodName.startsWith("set")) {
                continue;
            }
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                String typeName = parameterType.getName();
                if (NOT_ALLOW_TYPE.contains(typeName)) {
                    throw new DynamicParseException("实体类中["+methodName+"]方法出现了不允许出现的类型["+typeName+"], 应改用对应的包装类型");
                }
            }
        }
    }



}
